﻿' ******************************************************************************
' ** 
' **  Yahoo Finance Managed
' **  Written by Marius Häusler 2010
' **  It would be pleasant, if you contact me when you are using this code.
' **  Contact: YahooFinanceManaged@gmail.com
' **  Project Home: http://code.google.com/p/yahoo-finance-managed/
' **  
' ******************************************************************************
' **  
' **  Copyright 2010 Marius Häusler
' **  
' **  Licensed under the Apache License, Version 2.0 (the "License");
' **  you may not use this file except in compliance with the License.
' **  You may obtain a copy of the License at
' **  
' **    http://www.apache.org/licenses/LICENSE-2.0
' **  
' **  Unless required by applicable law or agreed to in writing, software
' **  distributed under the License is distributed on an "AS IS" BASIS,
' **  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
' **  See the License for the specific language governing permissions and
' **  limitations under the License.
' ** 
' ******************************************************************************


Namespace Base

    ''' <summary>
    ''' Provides methods and properties for downloading data and controlling these download processes. This class must be inherited.
    ''' </summary>
    ''' <remarks>This class can not be instanced outside of the library</remarks>
    Public MustInherit Class Download
     
        Protected Event AsyncStreamDownloadCompleted(ByVal sender As Download, ByVal ba As StreamDownloadCompletedEventArgs)

        Friend ReadOnly mHelper As New MyHelper
        Private ReadOnly mWebClients As New List(Of TimeoutWebClient)
        Private mProxy As IWebProxy = WebProxy.GetDefaultProxy
        Private mTimeout As Integer = 30000

        ''' <summary>
        ''' Gets the user arguments of each download process.
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks>If you just need a single object, you should use UserArgs(Int32) property, because of higher performance.</remarks>
        Public ReadOnly Property UserArgs() As Object()
            Get
                If mWebClients.Count > 0 Then
                    Dim lst(mWebClients.Count - 1) As Object
                    For i As Integer = 0 To mWebClients.Count - 1
                        Dim ea As DownloadEventArgs = TryCast(mWebClients(i).AsyncArgs, DownloadEventArgs)
                        If ea IsNot Nothing Then
                            lst(i) = ea.UserArgs
                        Else
                            lst(i) = mWebClients(i).AsyncArgs
                        End If
                    Next
                    Return lst
                Else
                    Return New Object() {}
                End If
            End Get
        End Property
        ''' <summary>
        ''' Gets the user argument of a download process at a specific index position.
        ''' </summary>
        ''' <param name="index">The index position of the download</param>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public ReadOnly Property UserArgs(ByVal index As Integer) As Object
            Get
                If index < 0 Or index > mWebClients.Count - 1 Then
                    Throw New IndexOutOfRangeException("The index is out of download list range.")
                Else
                    Return Me.GetDeeperObject(mWebClients(index).AsyncArgs)
                End If
            End Get
        End Property

        ''' <summary>
        ''' Gets or sets the time in miliseconds when the download will cancel if the time reached before it is completed.
        ''' </summary>
        ''' <value>Timeout in miliseconds. If value is '-1', the next download wont have a timeout.</value>
        ''' <returns>The timeout in miliseconds.</returns>
        ''' <remarks></remarks>
        Public Property Timeout() As Integer
            Get
                Return mTimeout
            End Get
            Set(ByVal value As Integer)
                If value >= 0 Then : mTimeout = value
                Else : mTimeout = 0
                End If
            End Set
        End Property
        ''' <summary>
        ''' Gets or sets the used proxy setting for download.
        ''' </summary>
        ''' <value></value>
        ''' <returns>The actual setted proxy</returns>
        ''' <remarks>Default value are proxy settings from Internet Explorer/Windows Internet Settings.</remarks>
        Public Property Proxy() As IWebProxy
            Get
                Return mProxy
            End Get
            Set(ByVal value As IWebProxy)
                mProxy = value
            End Set
        End Property
        ''' <summary>
        ''' Gets the number of actually running asynchronous downloads.
        ''' </summary>
        ''' <value></value>
        ''' <returns>The number of active downloads</returns>
        ''' <remarks></remarks>
        Public ReadOnly Property AsyncDownloadsCount() As Integer
            Get
                Return mWebClients.Count
            End Get
        End Property

        Protected Function Download(ByVal url As String) As StreamResponse
            Try
                Dim str As IO.Stream = Nothing
                Using wc As New TimeoutWebClient(mTimeout)
                    If mProxy IsNot Nothing Then wc.Proxy = mProxy
                    Return wc.Download(url)
                End Using
            Catch ex As Exception
                Return New StreamResponse(New ConnectionInfo(New Net.WebException("Unkonown Error occured", ex, WebExceptionStatus.UnknownError, Nothing), mTimeout, 0, Date.Now), Nothing)
            End Try
        End Function
        Protected Sub DownloadAsync(ByVal url As String, ByVal args As Object)
            Dim wc As New TimeoutWebClient(mTimeout)
            If mProxy IsNot Nothing Then wc.Proxy = mProxy
            AddHandler wc.AsyncDownloadCompleted, AddressOf Me.AsyncDownload_Completed
            mWebClients.Add(wc)
            wc.DownloadAsync(url, args)
        End Sub

        ''' <summary>
        ''' Cancels all running downloads.
        ''' </summary>
        ''' <returns>TRUE, if all downloads were canceled. FALSE, if something goes wrong.</returns>
        ''' <remarks></remarks>
        Public Function CancelAsyncAll() As Boolean
            If mWebClients.Count > 0 Then
                For i As Integer = mWebClients.Count - 1 To 0 Step -1
                    Me.RemoveClient(mWebClients(i))
                Next
            End If
            Return mWebClients.Count = 0
        End Function
        ''' <summary>
        ''' Cancels all running downloads containing the passed user argument.
        ''' </summary>
        ''' <param name="userArgs">The user argument that the download object contains.</param>
        ''' <returns>The number of canceled downloads.</returns>
        ''' <remarks></remarks>
        Public Function CancelAsync(ByVal userArgs As Object) As Integer
            If mWebClients.Count > 0 Then
                Dim count As Integer = 0
                For i As Integer = mWebClients.Count To 0 Step -1
                    If Me.DeepUserArgsEqual(mWebClients(i).AsyncArgs, userArgs) Then
                        If Me.RemoveClient(mWebClients(i)) Then count += 1
                    End If
                Next
                Return count
            Else
                Return 0
            End If
        End Function
        ''' <summary>
        ''' Cancels a running download at specific index position.
        ''' </summary>
        ''' <param name="index">The index position of the download process in queue.</param>
        ''' <returns>TRUE, if the download at the index position was canceled, FALSE, if not.</returns>
        ''' <remarks></remarks>
        Public Function CancelAsyncAt(ByVal index As Integer) As Boolean
            Return Me.RemoveClient(mWebClients(index))
        End Function
        ''' <summary>
        ''' Proves if the download processes contains the passed user argument.
        ''' </summary>
        ''' <param name="userArgs">Individual user argument that the download contains.</param>
        ''' <returns>The number of download processes containing the passed user argument.</returns>
        ''' <remarks></remarks>
        Public Function Contains(ByVal userArgs As Object) As Integer
            Dim count As Integer = 0
            For Each wc As TimeoutWebClient In mWebClients
                If Me.DeepUserArgsEqual(wc.AsyncArgs, userArgs) Then count += 1
            Next
            Return count
        End Function

        Private Sub AsyncDownload_Completed(ByVal sender As TimeoutWebClient, ByVal ea As StreamDownloadCompletedEventArgs)
            If mWebClients.Contains(sender) Then mWebClients.Remove(sender)
            RaiseEvent AsyncStreamDownloadCompleted(Me, ea)
            Me.RemoveClient(sender)
        End Sub

        Private Function RemoveClient(ByVal wc As TimeoutWebClient) As Boolean
            If wc IsNot Nothing Then
                RemoveHandler wc.AsyncDownloadCompleted, AddressOf AsyncDownload_Completed
                wc.Dispose()
                If mWebClients.Contains(wc) Then Return mWebClients.Remove(wc)
            End If
            Return False
        End Function
        Private Function DeepUserArgsEqual(ByVal argsX As Object, ByVal argsY As Object) As Boolean
            Return Me.GetDeeperObject(argsX) Is Me.GetDeeperObject(argsY)
        End Function
        Private Function GetDeeperObject(ByVal args As Object) As Object
            Dim deeperObj As Object = IIf(args IsNot Nothing AndAlso TypeOf args Is DownloadEventArgs, DirectCast(args, DownloadEventArgs).UserArgs, args)
            If deeperObj IsNot Nothing AndAlso TypeOf deeperObj Is DownloadEventArgs Then deeperObj = Me.GetDeeperObject(deeperObj)
            Return deeperObj
        End Function

    End Class



    Public Class StreamDownloadCompletedEventArgs
        Inherits DownloadCompletedEventArgs
        Public Overloads ReadOnly Property Response() As StreamResponse
            Get
                Return DirectCast(MyBase.Response, StreamResponse)
            End Get
        End Property
        Public Sub New(ByVal userArgs As Object, ByVal response As StreamResponse)
            MyBase.New(userArgs, response)
        End Sub
    End Class


    Public Class StreamResponse
        Inherits Response
        Public Overloads ReadOnly Property Result() As IO.MemoryStream
            Get
                Return TryCast(MyBase.Result, IO.MemoryStream)
            End Get
        End Property
        Public Sub New(ByVal info As ConnectionInfo, ByVal result As IO.MemoryStream)
            MyBase.New(info, result)
        End Sub
    End Class


End Namespace
